package com.hd123.rumba.gwt.widget2e.client.form;

import java.util.List;

import com.google.gwt.dom.client.Document;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.DomEvent;
import com.google.gwt.event.dom.client.HasChangeHandlers;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.ChangeListener;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.ListenerWrapper;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.SourcesChangeEvents;
import com.hd123.rumba.gwt.base.client.BUCN;
import com.hd123.rumba.gwt.base.client.BWithUCN;
import com.hd123.rumba.gwt.widget2.client.dialog.RMsgBox;
import com.hd123.rumba.gwt.widget2.client.dialog.RDialog.ButtonConfig;
import com.hd123.rumba.gwt.widget2.client.fielddef.FieldDef;
import com.hd123.rumba.gwt.widget2.client.fielddef.HasFieldDef;
import com.hd123.rumba.gwt.widget2.client.form.HasRValidator;
import com.hd123.rumba.gwt.widget2.client.form.RFieldComposite;
import com.hd123.rumba.gwt.widget2.client.form.RTextBox;
import com.hd123.rumba.gwt.widget2.client.form.RValidator;
import com.hd123.rumba.gwt.widget2.client.grid.RPageData;
import com.hd123.rumba.gwt.widget2.client.tip.RTip;
import com.hd123.rumba.gwt.widget2.client.tip.RTipButton;
import com.hd123.rumba.gwt.widget2e.client.dialog.RBrowserCallback;
import com.hd123.rumba.gwt.widget2e.client.dialog.RBrowserDialog;
import com.hd123.rumba.gwt.widget2e.client.dialog.RBrowserWithFilterCallback;
import com.hd123.rumba.gwt.widget2e.client.dialog.RFilterCallback;

/**
 * 组合控件：代码输入框+名称显示框+浏览按钮
 * 
 * @author LiQi
 * 
 */
@SuppressWarnings("deprecation")
public class RUCNBox extends RFieldComposite implements SourcesChangeEvents, HasChangeHandlers,
    HasRValidator, HasFieldDef {

  private static final String STYLENAME = "rb-UCNBox";
  private static final String STYLENAME_BROWSEBUTTON = "rb-UCNBox-browsebutton";

  private static final String IMAGE_BROWSEBUTTON = "images/form/browser_small.gif";
  private static final String IMAGE_BROWSEBUTTON_DISABLED = "images/form/browser_small_dis.gif";

  /** 根面板 */
  private FlowPanel root;

  /** 代码输入框 */
  private RTextBox codeBox;

  /** 名称显示框 */
  private RTextBox nameBox;

  /** 浏览按钮 */
  private SimplePanel browseButton;

  /** 浏览按钮图片 */
  private Image browseButtonImage;

  /** 浏览对话框 */
  protected RBrowserDialog browser;

  /** 浏览对话框标题 */
  private String browserCaption;

  /** 浏览对话框表格列标题 */
  private String[] browserColumnCaptions;

  /** 回调 */
  private RUCNCallback ucnCallback;

  /** 筛选回调 */
  private RFilterCallback filterCallback;

  /** 提示信息按钮 */
  private RTipButton tipButton;

  /** 数据 */
  private BUCN value;

  /**
   * @param browserCaption
   *          浏览对话框标题
   * @param browserColumnCaptions
   *          浏览对话框表格列标题
   * @param callback
   *          回调
   */
  public RUCNBox(String browserCaption, String[] browserColumnCaptions, RUCNCallback callback) {
    this(browserCaption, browserColumnCaptions, callback, null);
  }

  /**
   * @param browserColumnCaptions
   *          浏览对话框表格列标题
   * @param callback
   *          回调
   */
  public RUCNBox(String[] browserColumnCaptions, RUCNCallback callback) {
    this(null, browserColumnCaptions, callback, null);
  }

  /**
   * @param browserCaption
   *          浏览对话框标题
   * @param browserColumnCaptions
   *          浏览对话框表格列标题
   * @param callback
   *          回调
   */
  public RUCNBox(String browserCaption, String[] browserColumnCaptions,
      RUCNWithFilterCallback callback) {
    this(browserCaption, browserColumnCaptions, (RUCNCallback) callback, (RFilterCallback) callback);
  }

  /**
   * @param browserColumnCaptions
   *          浏览对话框表格列标题
   * @param callback
   *          回调
   */
  public RUCNBox(String[] browserColumnCaptions, RUCNWithFilterCallback callback) {
    this(null, browserColumnCaptions, callback);
  }

  /**
   * @param browserColumnCaptions
   *          浏览对话框表格列标题
   * @param ucnCallback
   *          UCN回调
   * @param filterCallback
   *          筛选回调，若只需要代码和名称的筛选，可使用RCodeNameFilterCallbackImpl
   */
  public RUCNBox(String[] browserColumnCaptions, RUCNCallback ucnCallback,
      RFilterCallback filterCallback) {
    this(null, browserColumnCaptions, ucnCallback, filterCallback);
  }

  /**
   * @param browserCaption
   *          浏览对话框标题
   * @param browserColumnCaptions
   *          浏览对话框表格列标题
   * @param ucnCallback
   *          UCN回调
   * @param filterCallback
   *          筛选回调，若只需要代码和名称的筛选，可使用RCodeNameFilterCallbackImpl
   */
  public RUCNBox(String browserCaption, String[] browserColumnCaptions, RUCNCallback ucnCallback,
      RFilterCallback filterCallback) {
    this(browserColumnCaptions);
    setBrowserCaption(browserCaption);
    setBrowserColumnCaptions(browserColumnCaptions);
    setUCNCallback(ucnCallback);
    setFilterCallback(filterCallback);
  }

  /**
   * 本构造方法不允许外界调用，是因为对使用者来说，ucnCallback是必须的，所以要求使用者在构造方法中传入。
   * 而本方法允许派生类调用是因为允许派生类对ucnCallback做一些改变 ，参见
   * {@link RSimpleUCNBox}
   * 
   * @param browserColumnCaptions
   *          浏览对话框表格列标题
   */
  protected RUCNBox(String[] browserColumnCaptions) {
    this();
    setBrowserColumnCaptions(browserColumnCaptions);
    buildBrowserDialog();
  }

  /**
   * 本方法的使用场景：界面上对同一实体同时存在RUCNBox和RBrowseBox，希望这两种控件能共享同一个browser
   * ，以减少冗余代码。此时可把事先构造好的browser传入。
   * 
   * @param browser
   *          browser的回调中onSelected方法会被重写，所以此方法只需提供空实现。
   * @param ucnCallback
   *          ucnCallback的fetchBrowserData和displayBrowserData两个方法会被传入的browser替代
   *          。 所以这两个方法只需提供空实现。ucnCallback.queryByCode仍然保持有效。
   */
  public RUCNBox(RBrowserDialog browser, RUCNCallback ucnCallback) {
    this();
    setBrowser(browser);
    setUCNCallback(ucnCallback);
  }

  /**
   * 私有的构造方法，供公开的构造方法调用
   * 
   */
  private RUCNBox() {
    super();

    value = new BUCN();

    root = new FlowPanel();
    root.setStyleName(STYLENAME);

    codeBox = new CodeBox();
    codeBox.addChangeHandler(new CodeBoxHandler());
    root.add(codeBox);

    nameBox = new RTextBox();
    nameBox.setReadOnly(true);
    root.add(nameBox);

    browseButton = new SimplePanel();
    browseButton.setStyleName(STYLENAME_BROWSEBUTTON);
    root.add(browseButton);

    browseButtonImage = new Image(IMAGE_BROWSEBUTTON);
    browseButtonImage.addClickHandler(new BrowseButtonHandler());
    browseButton.setWidget(browseButtonImage);

    initWidget(root);
  }

  /**
   * 构造浏览对话框
   * 
   */
  protected void buildBrowserDialog() {
    browser = new RBrowserDialog(browserCaption, browserColumnCaptions, new RBrowserCallback() {
      public void fetchData(int page, int pageSize, String sortField, String sortDir,
          AsyncCallback<RPageData> callback) {
        ucnCallback.fetchBrowserData(page, pageSize, sortField, sortDir, callback);
      }

      public String displayData(Object data, int col) {
        return ucnCallback.displayBrowserData(data, col);
      }

      public void onSelected(Object object) {
        if (!(object instanceof BWithUCN))
          throw new RuntimeException("返回值应为BWithUCN");
        onBrowserReturn((BWithUCN) object);
      }
    });
    browser.setFilterCallback(filterCallback);
  }

  /**
   * 浏览对话框onselected时触发，供派生类调用或修改行为
   * 
   */
  protected void onBrowserReturn(BWithUCN object) {
    if (getValue().getUuid() == null || !getValue().getUuid().equals(object.getUuid())) {
      setValue(object);
      validate();
      if (isValid())
        fireChange();
      else
        setValue(new BUCN());
    }
  }

  /**
   * 浏览对话框
   * 
   * @return
   */
  public RBrowserDialog getBrowser() {
    return browser;
  }

  /**
   * 设置浏览对话框
   * <p>
   * 请慎用此方法，一旦调用了setBrowser，原先的browserCaption,
   * browserColumnCaption, ucnCallback的
   * fetchBrowserData和displayBrowserData两个方法都将失效被传入的browser替代
   * 。ucnCallback.queryByCode仍然保持有效。
   * <p>
   * 本方法的使用场景：界面上对同一实体同时存在RUCNBox和RBrowseBox，希望这两种控件能共享同一个browser
   * ，以减少冗余代码。此时 可把事先构造好的browser传入。
   * 
   * @param browser
   *          browser的回调中onSelected方法会被重写，所以此方法只需提供空实现。
   */
  public void setBrowser(RBrowserDialog browser) {
    this.browser = browser;

    // 修改browser的callback
    final RBrowserCallback oldCallback = browser.getBrowserCallback();
    browser.setBrowserCallback(new RBrowserCallback() {

      public String displayData(Object data, int col) {
        return oldCallback.displayData(data, col);
      }

      public void fetchData(int page, int pageSize, String sortField, String sortDir,
          AsyncCallback callback) {
        oldCallback.fetchData(page, pageSize, sortField, sortDir, callback);
      }

      public void onSelected(Object object) {
        if (!(object instanceof BWithUCN))
          throw new RuntimeException("返回值应为BWithUCN");
        onBrowserReturn((BWithUCN) object);
      }
    });
  }

  /**
   * 设置浏览对话框标题
   * 
   * @param browserCaption
   */
  public void setBrowserCaption(String browserCaption) {
    this.browserCaption = browserCaption;
    if (browser != null)
      browser.setCaption(browserCaption);
  }

  /**
   * 设置浏览对话框表格列标题
   * 
   * @param browserColumnCaptions
   */
  public void setBrowserColumnCaptions(String[] browserColumnCaptions) {
    this.browserColumnCaptions = browserColumnCaptions;
    if (browser != null)
      browser.setColumnCaptions(browserColumnCaptions);
  }

  /**
   * 设置浏览对话框回调
   * 
   * @param ucnCallback
   */
  public void setUCNCallback(RUCNCallback ucnCallback) {
    this.ucnCallback = ucnCallback;
  }

  /**
   * 设置筛选回调
   * 
   * @param filterCallback
   * @see {@link RBrowserWithFilterCallback}
   */
  public void setFilterCallback(RFilterCallback filterCallback) {
    this.filterCallback = filterCallback;
    if (browser != null)
      browser.setFilterCallback(filterCallback);
  }

  /**
   * 取得控件所包含的提示信息对象。
   * 
   * @return
   */
  public RTip getTip() {
    if (tipButton == null) {
      tipButton = new RTipButton();
      tipButton.bind(codeBox.getTip());
      root.add(tipButton);
    }
    return codeBox.getTip();
  }

  /**
   * BUCN数据
   * 
   * @return 永远不会返回null
   */
  public BUCN getValue() {
    return value;
  }

  /**
   * 设置BUCN数据
   * 
   * @param value
   */
  public void setValue(BWithUCN value) {
    if (value == null) {
      this.value.setUuid(null);
      this.value.setCode(null);
      this.value.setName(null);
    } else {
      this.value.setUuid(value.getUuid());
      this.value.setCode(value.getCode());
      this.value.setName(value.getName());
    }
    refresh();
  }

  /**
   * 清空取值。清空后getValue()将返回一个uuid、code、name均为null的BUCN。
   */
  public void clearValue() {
    setValue(null);
  }

  /**
   * 代码框
   * 
   * @return
   */
  public RTextBox getCodeBox() {
    return codeBox;
  }

  /**
   * 名称框
   * 
   * @return
   */
  public RTextBox getNameBox() {
    return nameBox;
  }

  public void setRequired(boolean required) {
    super.setRequired(required);
    if (getFieldDef() != null)
      getFieldDef().setNullable(!required);
  }

  /**
   * 刷新显示
   * 
   */
  private void refresh() {
    codeBox.setValue(value.getCode());
    nameBox.setValue(value.getName());
  }

  /**
   * 是否只读
   * 
   * @return
   */
  public boolean isReadOnly() {
    return codeBox.isReadOnly();
  }

  /**
   * 设置是否只读
   * 
   * @param readOnly
   */
  public void setReadOnly(boolean readOnly) {
    codeBox.setReadOnly(readOnly);
    browseButtonImage.setUrl(readOnly ? IMAGE_BROWSEBUTTON_DISABLED : IMAGE_BROWSEBUTTON);
  }

  /**
   * 设置代码框宽度
   * 
   * @param width
   */
  public void setCodeWidth(String width) {
    codeBox.setInputWidth(width);
  }

  /**
   * 设置名称框宽度
   * 
   * @param width
   */
  public void setNameWidth(String width) {
    nameBox.setInputWidth(width);
  }

  /**
   * @deprecated 请用{@link #addChangeHandler}代替
   */
  @Deprecated
  public void addChangeListener(ChangeListener listener) {
    ListenerWrapper.WrappedChangeListener.add(this, listener);
  }

  /**
   * @deprecated 请用{@link #addChangeHandler}返回的
   *             {@link HandlerRegistration#removeHandler}代替
   */
  @Deprecated
  public void removeChangeListener(ChangeListener listener) {
    ListenerWrapper.WrappedChangeListener.remove(this, listener);
  }

  public HandlerRegistration addChangeHandler(ChangeHandler handler) {
    return addDomHandler(handler, ChangeEvent.getType());
  }

  /**
   * 触发change事件
   * 
   */
  protected void fireChange() {
    DomEvent.fireNativeEvent(Document.get().createChangeEvent(), this);
  }

  public RValidator getValidator() {
    return codeBox.getValidator();
  }

  public void setValidator(RValidator validator) {
    codeBox.setValidator(validator);
  }

  public void clearValidResults() {
    codeBox.clearValidResults();
  }

  public boolean isValid() {
    return codeBox.isValid();
  }

  public List getInvalidMessages() {
    return codeBox.getInvalidMessages();
  }

  public boolean validate() {
    if (isReadOnly() || !isEnabled() || !isVisible())
      return true;
    return codeBox.validate();
  }

  public FieldDef getFieldDef() {
    return codeBox.getFieldDef();
  }

  public void setFieldDef(FieldDef fieldDef) {
    if (fieldDef == null)
      return;

    setFieldCaption(fieldDef.getCaption());
    setRequired(fieldDef.isRequired());
    codeBox.setFieldDef(fieldDef);
  }

  /**
   * 代码框
   * 
   * @author LiQi
   * 
   */
  private class CodeBox extends RTextBox {

    /** 返回tip前创建tipButton */
    public RTip getTip() {
      RTip tip = super.getTip();
      if (tipButton == null) {
        tipButton = new RTipButton();
        tipButton.bind(tip);
        root.add(tipButton);
      }
      return tip;
    }

    /** 若代码框验证失败，则清空名称框 */
    public boolean validate() {
      boolean valid = super.validate();
      if (!valid) {
        nameBox.setText("");
        RUCNBox.this.setValue(new BUCN());
      }
      return valid;
    }
  }

  /**
   * 浏览按钮回调
   * 
   * @author LiQi
   * 
   */
  private class BrowseButtonHandler implements ClickHandler {
    public void onClick(ClickEvent event) {
      if (isReadOnly())
        return;
      browser.center();
    }
  }

  /**
   * 代码框事件处理
   * 
   * @author LiQi
   * 
   */
  private class CodeBoxHandler implements ChangeHandler {
    public void onChange(ChangeEvent event) {
      final String code = codeBox.getValue();
      if (code.equals("")) {
        // 若代码被清空，则清空数据
        setValue(new BUCN());
        fireChange();

      } else {
        ucnCallback.queryByCode(code, new AsyncCallback<BWithUCN>() {
          // 查找出错时清空数据
          public void onFailure(Throwable caught) {
            RMsgBox.showError("查找" + code + "时出错", caught, new RMsgBox.Callback() {
              public void onClosed(ButtonConfig clickedButton) {
                if (getValue().getUuid() != null) {
                  setValue(new BUCN());
                  fireChange();
                } else {
                  refresh();
                }
              }
            });
          }

          // 查找成功则设置返回的数据
          public void onSuccess(BWithUCN result) {
            if (result == null) {
              // 返回null说明未找到
              if (getValue().getUuid() != null) {
                setValue(new BUCN());
                fireChange();
              } else {
                refresh();
              }
            } else if (!(result instanceof BWithUCN))
              throw new RuntimeException("返回值应为BWithUCN");
            else {
              setValue(result);
              fireChange();
            }
          }
        });
      }
    }
  }
}
